#include <assert.h>
#include <allegro.h>
#include "game.h"
#include "main.h"
#include "map.h"
#include "gui.h"
#include "network.h"
#include "logic.h"
#include "dungeon.h"
#include "bailout.h"
#include "chat.h"
#include "items.h"
#include "resources.h"
#include "colors.h"
#include "mt.h"

#define MAX_ENEMIES
#define MAX_CHAT_LENGTH 256
#define KEY_ATTACK_CLOSE KEY_D
#define KEY_ATTACK_RANGE KEY_S

static Map* map = NULL;
static Hud* hud = NULL;
int controlled_player; // not just displayed but also controlled
static CtoSmessage netmessage; // For communications to server
static StoCmessage received;  // For communications to server
static unsigned long seed;
static enum { INPUT_GAME, INPUT_CHOICE, INPUT_CHAT } input_state;
int choice_found; // Offer this item for INPUT_CHOICE
int choice_select;

void game_damage_handler (int time, coord_t where, int attacker, int victim, int weapon, int damage);

void game_init()
{
	input_state = INPUT_GAME;
	clear_keybuf();

	for (int i = 0; i < MAX_PLAYERS; i++) {
		player_init (&player[i]);
		memset (&player[i].pos, 0, sizeof(player[i].pos));
		memset (&player[i].action, 0, sizeof(player[i].action));
		memset (&player[i].want_action, 0, sizeof(player[i].want_action));
		player[i].checkpoint = (coord_t) { 2 + i*2, 7, 0 };
		player[i].pos.x = player[i].checkpoint.x;
		player[i].pos.y = player[i].checkpoint.y;
		player[i].pos.z = player[i].checkpoint.z;
		
		#if TOWERHACK_DEBUG
		int item = mt_randint (WEAPONS_CLOSE_BEGIN, WEAPONS_CLOSE_END);
		player[i].inv.slot[items[item].type - ITEM_WEAPON_CLOSE] = item;
		#endif
	}
	
	if (map == NULL) {
		map = malloc(sizeof(Map));
		map_init (map, MAP_WIDTH, MAP_HEIGHT);
	}

	if (hud == NULL) {
		hud = hud_create (&player[controlled_player]);
	}
	
	choice_found = 0;
	choice_select = 0;
	
	chat_init ();
	logic_reset ();
	
	damage_handler = &game_damage_handler; // Set handler for logic
}

// Creates a map, maybe?
void game_seed(unsigned long s)
{
	seed = s;
	
	if (map == NULL) {
		map = malloc(sizeof(Map));
		map_init (map, MAP_WIDTH, MAP_HEIGHT);
	}
	generate_amazing_maze (map, s, NUM_DUNGEON_SEGMENTS);
	map_decorate (map); 
}

static void send_chat(const char* text) 
{
	CtoSmessage msg;
	msg.type = CtoS_CHAT;
	strcpy (msg.line, text);
	send_CtoSmessage(&msg);
}

void send_action (const Action* a)
{
	CtoSmessage msg;
	msg.type = CtoS_INGAME_ACTION;
	msg.action = *a;
	send_CtoSmessage(&msg);
}

void get_kbinput (int* key_press, int* key_release)
{
	static int key_memory [4] = {0,0,0,0}; // ↑↓←→ D S (ENTER)
	
	if ((player[controlled_player].action.type != ACTION_MOVE) &&
	    (player[controlled_player].want_action.type != ACTION_MOVE))
			key_memory[0] = 0;
	
	*key_press = *key_release = 0;
	if ((key_memory[0] != KEY_UP)    && key[KEY_UP])    { *key_press = KEY_UP;    key_memory[0] = KEY_UP; key[KEY_DOWN] = key[KEY_LEFT] = key[KEY_RIGHT] = 0; return; }
	if ((key_memory[0] != KEY_DOWN)  && key[KEY_DOWN])  { *key_press = KEY_DOWN;  key_memory[0] = KEY_DOWN; key[KEY_UP] = key[KEY_LEFT] = key[KEY_RIGHT] = 0; return; }
	if ((key_memory[0] != KEY_LEFT)  && key[KEY_LEFT])  { *key_press = KEY_LEFT;  key_memory[0] = KEY_LEFT; key[KEY_UP] = key[KEY_DOWN] = key[KEY_RIGHT] = 0; return; }
	if ((key_memory[0] != KEY_RIGHT) && key[KEY_RIGHT]) { *key_press = KEY_RIGHT; key_memory[0] = KEY_RIGHT; key[KEY_UP] = key[KEY_DOWN] = key[KEY_LEFT] = 0; return; }
	
	if ((key_memory[0] == KEY_UP)    && !key[KEY_UP])    { *key_release = KEY_UP;    key_memory[0] = 0; return; }
	if ((key_memory[0] == KEY_DOWN)  && !key[KEY_DOWN])  { *key_release = KEY_DOWN;  key_memory[0] = 0; return; }
	if ((key_memory[0] == KEY_LEFT)  && !key[KEY_LEFT])  { *key_release = KEY_LEFT;  key_memory[0] = 0; return; }
	if ((key_memory[0] == KEY_RIGHT) && !key[KEY_RIGHT]) { *key_release = KEY_RIGHT; key_memory[0] = 0; return; }
	
	if (!key_memory[1] && key[KEY_ATTACK_CLOSE]) { *key_press = KEY_ATTACK_CLOSE; key_memory[1] = 1; return; }
	if (!key_memory[2] && key[KEY_ATTACK_RANGE]) { *key_press = KEY_ATTACK_RANGE; key_memory[2] = 1; return; }
	if (!key_memory[3] && key[KEY_ENTER])        { *key_press = KEY_ENTER;        key_memory[3] = 1; return; }
	
	if (key_memory[1] && !key[KEY_ATTACK_CLOSE]) { key_memory[1] = 0; return; }
	if (key_memory[2] && !key[KEY_ATTACK_RANGE]) { key_memory[2] = 0; return; }
	if (key_memory[3] && !key[KEY_ENTER])        { key_memory[3] = 0; return; }
}

void game_update(void)
{
	Action a = no_action;
	
	receive_StoCmessage (&received);
	while (received.type != StoC_NOMSG) {
		switch (received.type) {
			
		case StoC_INGAME_ACTION:
			assert ((received.action.actor >= 0) && (received.action.actor < MAX_PLAYERS));
			if (received.action.actor != controlled_player) {
				logic_enqueue (&received.action);
			} else {
				action_free (&received.action);
			}
			break;
			
		case StoC_INGAME_DAMAGE:
			if (actor_type (received.damage.victim) == ACTOR_PLAYER) {
				Player* p = (Player*) player_from_actor_id (received.damage.victim);
				p->hp = received.damage.hp;
				#if TOWERHACK_DEBUG
				fprintf (stderr, "Game: deal %d damage from %d to %d. Victim hp is now %d\n",
					received.damage.damage, received.damage.attacker,
					received.damage.victim, received.damage.hp);
				#endif
			}
			break;
			
		case StoC_INGAME_DIE:
			if (actor_type (received.damage.victim) == ACTOR_PLAYER) {
				Player* p = (Player*) player_from_actor_id (received.damage.victim);
				p->action = action_alloc_die (received.damage.victim, game_time);
			}
			break;
			
		case StoC_INGAME_SETPLAYER:
			assert ((received.who >= 0) && (received.who < MAX_PLAYERS));
			player[received.who].skill.level = received.level;
			player[received.who].skill.max_hp = received.hp;
			memcpy(&player[received.who].inv, &received.inv, sizeof(Inventory));
			break;

		case StoC_CHAT:
			chat_receive(received.line);
			break;
			
		default:
			bailout ("sds", "Received unidentified message type ", received.type, " from server.");
			break;
			
		}
		
		receive_StoCmessage (&received);
	}
	
	Player* p = &player[controlled_player];
	char* line;
	int key_press, key_release;
	
	switch (input_state)
	{
		
	case INPUT_GAME:
		get_kbinput (&key_press, &key_release);
	
		if ((key_press == KEY_ATTACK_CLOSE) && (p->inv.slot[INV_WEAPON_CLOSE] != ITEM_NOTHING)) 
			a = action_alloc_closeattack (controlled_player, time_asap(p), p->inv.slot[INV_WEAPON_CLOSE]);
		else if ((key_press == KEY_ATTACK_RANGE) && (p->inv.slot[INV_WEAPON_RANGE] != ITEM_NOTHING))
			a = action_alloc_rangeattack (controlled_player, time_asap(p), p->inv.slot[INV_WEAPON_RANGE]);
		else if (key_press == KEY_LEFT)  a = action_alloc_turn (controlled_player, time_asap(p), DIR_LEFT);
		else if (key_press == KEY_RIGHT) a = action_alloc_turn (controlled_player, time_asap(p), DIR_RIGHT);
		else if (key_press == KEY_UP)    a = action_alloc_turn (controlled_player, time_asap(p), DIR_UP);
		else if (key_press == KEY_DOWN)  a = action_alloc_turn (controlled_player, time_asap(p), DIR_DOWN);
		else if ((key_release == KEY_LEFT) || (key_release == KEY_RIGHT) || (key_release == KEY_UP) || (key_release == KEY_DOWN))
			a = action_alloc_halt (controlled_player, time_asap(p));

		if ((a.type != ACTION_NONE) && (action_is_valid (map, &a))) {
			logic_enqueue (&a);
			send_action (&a);
		} else {
			action_free (&a);
		}
	
		if (keypressed() && key[KEY_ENTER]) {
			input_state = INPUT_CHAT; 
			clear_keybuf();
		}
		break;
		
	case INPUT_CHAT:
		if ((line = chat_input()) != NULL) {
			input_state = INPUT_GAME;
			key[KEY_ENTER] = 0;
                        if (strlen(line) > 0) {
			    send_chat(line);
                        }
		}
		break;
		
	case INPUT_CHOICE:
		if (!keypressed()) break;
		if ((key[KEY_LEFT]) || (key[KEY_RIGHT])) choice_select = !choice_select;
		if (key[KEY_S]) {
			netmessage.type = CtoS_INGAME_SETINVENTORY;
			netmessage.player = controlled_player;
			memcpy (&netmessage.inv, &player[controlled_player].inv, sizeof(Inventory));
			if (choice_select)
				netmessage.inv.slot[items[choice_found].type - ITEM_WEAPON_CLOSE] = choice_found;
			send_CtoSmessage (&netmessage);
			input_state = INPUT_GAME;
		}
		clear_keybuf();
		break;
		
	}

	if (game_time % (FPS/10) == 0) map->anim_frame++;
	logic_update (map);
	hud_update (hud);
	chat_update_messages ();
	for (int i = 0; i < MAX_EFFECTS; i++) effect_update (&map->effect[i]);
}

void display_choice () 
{
	rectfill(buffer, SCREEN_W/2-110, SCREEN_H/2-55, SCREEN_W/2-1, SCREEN_H/2+54, choice_select ? CHOICEWIN_COL : CHOICEWIN_SELECT_COL);
	rectfill(buffer, SCREEN_W/2, SCREEN_H/2-55, SCREEN_W/2+109, SCREEN_H/2+54, choice_select ? CHOICEWIN_SELECT_COL : CHOICEWIN_COL);
	if (!choice_select) {
		rectfill(buffer, SCREEN_W/2-105, SCREEN_H/2-50, SCREEN_W/2-6, SCREEN_H/2+49, CHOICEWIN_SELECT_BG_COL);
		rect (buffer, SCREEN_W/2-55-9, SCREEN_H/2-9, SCREEN_W/2-55+8, SCREEN_H/2+8, CHOICEWIN_SELECT_COL);
	} else {
		rectfill(buffer, SCREEN_W/2+5, SCREEN_H/2-50, SCREEN_W/2+104, SCREEN_H/2+49, CHOICEWIN_SELECT_BG_COL);
		rect (buffer, SCREEN_W/2+55-9, SCREEN_H/2-9, SCREEN_W/2+55+8, SCREEN_H/2+8, CHOICEWIN_SELECT_COL);
	}
	textprintf_centre_ex(buffer, font, SCREEN_W/2-55, SCREEN_H/2-45, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "Keep");
	
	int old_item = player[controlled_player].inv.slot[items[choice_found].type - ITEM_WEAPON_CLOSE];
	textprintf_right_ex(buffer, font, SCREEN_W/2-5, SCREEN_H/2-35, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "%s", items[old_item].name);
	textprintf_centre_ex(buffer, font, SCREEN_W/2+55, SCREEN_H/2-45, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "Pick up");
	textprintf_ex(buffer, font, SCREEN_W/2+5, SCREEN_H/2-35, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "%s", items[choice_found].name);
	masked_blit(bmp.icons, buffer, items[old_item].icon_x*16, items[old_item].icon_y*16, SCREEN_W/2-55-8, SCREEN_H/2-8, 16, 16);
	masked_blit(bmp.icons, buffer, items[choice_found].icon_x*16, items[choice_found].icon_y*16, SCREEN_W/2+55-8, SCREEN_H/2-8, 16, 16);
}

void game_draw(void)
{
	int map_offsetx;
	int map_offsety;
	acquire_bitmap (buffer);
	clear_bitmap (buffer);

	coord_t player_pos = player_pxpos (&player[controlled_player]);
	map_display(map, buffer, player_pos, &map_offsetx, &map_offsety);
	map_display_effects (map, buffer, player_pos);
	for (int i = 0; i < MAX_PLAYERS; i++) {
		if ((*player[i].data.nickname != 0) && (player[i].pos.z == player[controlled_player].pos.z))
			player_display(&player[i], map_offsetx, map_offsety);
	}
	map_display_fg (map, buffer, player_pos);
	chat_display_messages ();
	hud_draw (hud);
	if (input_state == INPUT_CHAT) {
		chat_display_input ();
	} 
	else if (input_state == INPUT_CHOICE) {
		display_choice ();
	}
	release_bitmap (buffer);
}

void game_cleanup(void)
{
	if (map != NULL) {
		map_cleanup(map);
		free(map);
		map = NULL;
	}
	input_state = INPUT_GAME;
}

void game_damage_handler (int time, coord_t where, int attacker, int victim, int weapon, int damage)
{
	fprintf (stderr, "DAMAGE! At time %d, location (%d,%d,%d), %d hit %d with %s, causing %d points of damage.\n",
		time, where.x, where.y, where.z, attacker, victim, items[weapon].name, damage);
	char dmgstr [20];
	sprintf (dmgstr, "%d", damage);
	map_effect (map, effect_make_damage (fnt.dmg_font, dmgstr, where));
}
